package de.lgohlke.helper;

import java.io.IOException;
import java.io.StringReader;

import javax.xml.namespace.QName;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;

import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

/**
 * <p>XPathHelper class.</p>
 *
 * @author lars
 * @version $Id: $
 */
public final class XPathHelper
{
  private XPathHelper()
  {
    // ok
  }

  private static XPath           xpath   = null;
  private static DocumentBuilder builder = null;

  private static Document getDocument(final String content) throws ParserConfigurationException, SAXException, IOException
  {
    InputSource is = new InputSource(new StringReader(content));

    if (builder == null)
    {
      DocumentBuilderFactory domFactory = DocumentBuilderFactory.newInstance();
      domFactory.setNamespaceAware(false); // never forget this!
      builder = domFactory.newDocumentBuilder();
    }

    return builder.parse(is);
  }

  private static XPathExpression getXPathExpression(final String xpathExpression) throws XPathExpressionException
  {
    if (xpath == null)
    {
      XPathFactory factory = XPathFactory.newInstance();
      xpath = factory.newXPath();
    }

    return xpath.compile(xpathExpression);
  }

  private static Object evaluateXPATH(final String content, final String xpathExpression, final QName nodeset) throws ParserConfigurationException,
      SAXException, IOException, XPathExpressionException
  {
    Document doc = getDocument(content);

    XPathExpression expr = getXPathExpression(xpathExpression);
    return expr.evaluate(doc, nodeset);
  }

  /**
   * <p>getXPathNodeList.</p>
   *
   * @param content a {@link java.lang.String} object.
   * @param xpathExpression a {@link java.lang.String} object.
   * @return a {@link org.w3c.dom.NodeList} object.
   * @throws javax.xml.parsers.ParserConfigurationException if any.
   * @throws org.xml.sax.SAXException if any.
   * @throws java.io.IOException if any.
   * @throws javax.xml.xpath.XPathExpressionException if any.
   */
  public static NodeList getXPathNodeList(final String content, final String xpathExpression) throws ParserConfigurationException, SAXException, IOException,
      XPathExpressionException
  {
    return (NodeList) evaluateXPATH(content, xpathExpression, XPathConstants.NODESET);
  }

  /**
   * <p>getXPathNode.</p>
   *
   * @param content a {@link java.lang.String} object.
   * @param xpathExpression a {@link java.lang.String} object.
   * @return a {@link org.w3c.dom.Node} object.
   * @throws javax.xml.parsers.ParserConfigurationException if any.
   * @throws org.xml.sax.SAXException if any.
   * @throws java.io.IOException if any.
   * @throws javax.xml.xpath.XPathExpressionException if any.
   */
  public static Node getXPathNode(final String content, final String xpathExpression) throws ParserConfigurationException, SAXException, IOException,
      XPathExpressionException
  {
    return (Node) evaluateXPATH(content, xpathExpression, XPathConstants.NODE);
  }

  /**
   * <p>getNodesHavingIDEndingWith.</p>
   *
   * @param content
   *          der Inhalt, der zu parsen ist
   * @param nodeType
   *          darf null sein, dann werden alle gesucht
   * @param suffix
   *          ein bestimmter Wert, auf den die ID endet
   * @throws javax.xml.xpath.XPathExpressionException if any.
   * @throws javax.xml.parsers.ParserConfigurationException if any.
   * @throws org.xml.sax.SAXException if any.
   * @throws java.io.IOException if any.
   * @return a {@link org.w3c.dom.NodeList} object.
   */
  public static NodeList getNodesHavingIDEndingWith(final String content, final String nodeType, final String suffix) throws XPathExpressionException,
      ParserConfigurationException, SAXException, IOException
  {
    String node = nodeType == null ? "*" : nodeType;
    String xpath = "//" + node + "[substring(@id, string-length(@id) - string-length('" + suffix + "') +1) = '" + suffix + "']/..";
    return getXPathNodeList(content, xpath);
  }
}
